/**
 * 文档与编译原理对应知识：
 *      文法属于LL（1）文法，
 *       若输入字符串a属于 Fisrt集，则使用相应的规则进行处理，
 *      如果输入字符串a不属于fisrt集，则：
 *          1.若fisrt集中包含ε，且 a属于follow集，则自动匹配ε，
 *          2. 否则，a的出现是语法错误
 * 
 *    
 */


import {Tokenizer,Token} from './tokenizer';
import {Types} from './types';
import {pushMembers,extend} from './utils'
var typesName = {}
typesName[Types.TK_TEXT] = "text node"
typesName[Types.TK_IF] = "{if}"
typesName[Types.TK_END_IF] = "{/if}"
typesName[Types.TK_ELSE_IF] = "{elseif ..}"
typesName[Types.TK_ELSE] = "{else}"
typesName[Types.TK_EACH] = "{each ... }"
typesName[Types.TK_END_EACH] = "{/each}"
typesName[Types.TK_GT] = ">"
typesName[Types.TK_SLASH_GT] = "/>"
typesName[Types.TK_TAG_NAME] = "open tag name"
typesName[Types.TK_ATTR_NAME] = "attribute name"
typesName[Types.TK_ATTR_EQUAL] = "="
typesName[Types.TK_ATTR_STRING] = "attribute string"
typesName[Types.TK_CLOSE_TAG] = "close tag"
typesName[Types.TK_EOF] = "EOF"
export class Parser{
    tokens:Tokenizer;
    constructor(input:string){
        this.tokens=new Tokenizer(input);
    }
    private error(str:string){
        throw new Error('Parse Error: ' + str);
    }
    private  parseError(name:string):void{
        let token=this.tokens.peekToken();
        this.error(`在${name}过程中，出现非法的票据：‘${token.label}’`);
    }
    private is(type:number):boolean {
        return (this.tokens.peekToken().type===type);
    }
    private eat(type:number):Token {
        let token=this.tokens.nextToken();
        if(token.type!==type){
            this.error('expect a(n) ' + typesName[type] + ', but got a(n) ' + typesName[token.type])
        }
        return token;
    }
    /**
     * parse
     */
    public parse() {
        this.tokens.index=0;   
        let root=this.parseStat();
        this.eat(Types.TK_EOF);
        return root;     
    }
    private parseStat() {
        let stat={
            type:"Stat",
            members:[]
        }
        /**
         * 这个if中的参数是根据FIRST集确定的，即：
         * FIRST(Stat) = {TK_IF, TK_EACH, TK_TAG_NAME, TK_TEXT}
         */
        if (
            this.is(Types.TK_IF)||
            this.is(Types.TK_EACH)||
            this.is(Types.TK_TAG_NAME)||
            this.is(Types.TK_TEXT)
        ) {
            /**
             * 这里面的操作是根据推导式来编写的：
             * Stat -> Frag Stat | ε
             */
            pushMembers(stat.members,[this.parseFrag()]);
            pushMembers(stat.members,this.parseStat().members)
        }else{
            //end
        }
        return stat;
    }
    private parseFrag() {
        if (this.is(Types.TK_IF)) {
            return this.parseIfStat();
        }
        else if (this.is(Types.TK_EACH)) {
           return this.parseEachStat();
        }else if (this.is(Types.TK_TAG_NAME)) {
            return this.parseNode();
        }else if (this.is(Types.TK_TEXT)) {
           let token=this.eat(Types.TK_TEXT);
           return token.label; 
        }else{
            this.parseError("parseFrag");
        }
    }
    /**
     * 根据
     * IfStat -> '{if ...}' Stat ElseIfs Else '{/if}'
     */
    private parseIfStat() {
        let token=this.tokens.peekToken();
        let ifStat={
            type:"IfStat",
            label:token.label,
            body:{},
            elseifs:{},
            elsebody:{}
        }
        this.eat(Types.TK_IF);//对应 '{if ...}'
        ifStat.body=this.parseStat();//对应 Stat
        ifStat.elseifs=this.parseElseIfs();//对应 ElseIfs
        ifStat.elsebody=this.parseElse();//对应 Else
        this.eat(Types.TK_END_IF);//对应 '{/if}'
        return ifStat;
    }
    /**
     * 根据
     * ElseIfs -> ElseIf ElseIfs | ε
     */
    private parseElseIfs():any {
        let elseifs=[];
        if (this.is(Types.TK_ELSE_IF)) {//处理  ElseIfs -> ElseIf ElseIfs 这一部分
            elseifs.push(this.parseElseIf());
            elseifs.push.apply(elseifs,this.parseElseIfs());
        }else if (
            this.is(Types.TK_ELSE)||
            this.is(Types.TK_END_IF)
        ) {//对应 ElseIfs -> ε部分，

        }else{
            this.parseError('parseElseIfs');
        }
        return elseifs;
    }
    private parseElseIf() {
        let token=this.tokens.peekToken();
        let elseif={
            type:"ElseIf",
            label:token.label,
            body:{}
        };
        this.eat(Types.TK_ELSE_IF);
        elseif.body=this.parseStat();
        return elseif;
    }
    private parseElse() {
        if (this.is(Types.TK_ELSE)) {
            this.eat(Types.TK_ELSE);
            return this.parseStat();
        }else if (
            this.is(Types.TK_END_IF)
        ) {
            
        }else{
            this.parseError('parseElse');
        }
    }
    private parseNode() {
        let token=this.tokens.peekToken();
        let node={
            type:'Node',
            name:token.label
        }
        this.parseOpenTag(node);
        this.parseNodeTail(node);
        return node;
    }
    private parseOpenTag(node:any) {
        this.eat(Types.TK_TAG_NAME);
        node.attributes=this.parseAttrs();
    }
    private parseNodeTail(node:any) {
        if (this.is(Types.TK_GT)) {
            this.eat(Types.TK_GT);
            node.body=this.parseStat();
            this.eat(Types.TK_CLOSE_TAG);
        }else if(this.is(Types.TK_SLASH_GT)){
            this.eat(Types.TK_SLASH_GT);
        }else{
            this.parseError('parseNodeTail');
        }
    }
    private parseAttrs() {
        let attrs={};
        if (this.is(Types.TK_ATTR_NAME)) {
            extend(attrs,this.parseAttr());
            extend(attrs,this.parseAttrs());
        } else if (
            this.is(Types.TK_GT)||
            this.is(Types.TK_SLASH_GT)
        ) {
            //do nothing
        }else{
            this.parseError("parseAttrs")
        }
        return attrs;
    }
    private parseAttr() {
        let attr={};
        let token=this.eat(Types.TK_ATTR_NAME);
        let value=this.parseValue();
        attr[token.label]=value;
        return attr;
    }
    private parseValue() {
        if (this.is(Types.TK_ATTR_EQUAL)) {
            this.eat(Types.TK_ATTR_EQUAL);
            let token=this.eat(Types.TK_ATTR_STRING);
            return token.label;
        }else if (
            this.is(Types.TK_GT)||
            this.is(Types.TK_SLASH_GT)||
            this.is(Types.TK_ATTR_NAME)
        ) {
            //do nothing
        }
         else {
            this.parseError("parseValue")
        }
    }
    private parseEachStat() {
        let eachStat={
            type:'EachStat',
            body:{},
            label:'',
        }
        let token=this.eat(Types.TK_EACH);
        eachStat.label=token.label;
        eachStat.body=this.parseStat();
        this.eat(Types.TK_END_EACH);
        return eachStat;
    }
}